/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "r_local.h"


#define MODELS_HASH_SIZE				MAX_MODELS / 4

static model_t *		r_modelsHashTable[MODELS_HASH_SIZE];
static model_t *		r_models[MAX_MODELS];
static int				r_numModels;


/*
 ==================
 R_FindTriangleNeighbor
 ==================
*/
static int R_FindTriangleNeighbor (int numTriangles, glIndex_t *indices, glIndex_t i1, glIndex_t i2, int ignore){

	glIndex_t	*index;
	int			neighbor = -1;
	int			i, count = 0;

	for (i = 0, index = indices; i < numTriangles; i++, index += 3){
		if ((index[0] == i1 && index[1] == i2) || (index[1] == i1 && index[2] == i2) || (index[2] == i1 && index[0] == i2)){
			if (++count > 2)
				return numTriangles;	// Edge shared by more than two triangles

			if (i != ignore)
				neighbor = i;
		}
		else if ((index[1] == i1 && index[0] == i2) || (index[2] == i1 && index[1] == i2) || (index[0] == i1 && index[2] == i2)){
			if (++count > 2)
				return numTriangles;	// Edge shared by more than two triangles
		}
	}

	if (neighbor == -1)
		return numTriangles;

	return neighbor;
}

/*
 ==================
 R_MultiplyJoints
 ==================
*/
static void R_MultiplyJoints (int numJoints, md5Joint_t *invJoints, odJointMat *joints, odJointMat *out){

	int		i;

	if (joints){
		// Multiply the joints by the inverse model joints
		for (i = 0; i < numJoints; i++)
			out[i] = joints[i] * invJoints[i].matrix;
	}
	else {
		// Set the joints to identity for default pose
		for (i = 0; i < numJoints; i++)
			out[i].Identity();
	}
}

/*
 ==================
 R_TransformVertices
 ==================
*/
static void R_TransformVertices (int numVertices, md5Vertex_t *vertices, md5Weight_t *weights, odJointMat *joints, glVertex_t *outVertices, odBounds &outBounds){

	md5Vertex_t	*vertex;
	md5Weight_t	*weight;
	odJointMat	weightedJoint;
	int			i, j;

	// Clear the bounds
	outBounds.Clear();

	for (i = 0, vertex = vertices; i < numVertices; i++, vertex++){
		// Compute a weighted joint for this vertex
		weight = weights + vertex->firstWeight;

		weightedJoint = joints[weight->joint] * weight->jointWeight;

		if (vertex->numWeights != 1){
			weight++;

			for (j = 1; j < vertex->numWeights; j++, weight++)
				weightedJoint += joints[weight->joint] * weight->jointWeight;
		}

		// Transform position, normal, and tangent vectors
		weightedJoint.TransformVector(vertex->baseXyz, outVertices[i].xyz);
		weightedJoint.TransformNormal(vertex->baseNormal, outVertices[i].normal);
		weightedJoint.TransformNormal(vertex->baseTangents[0], outVertices[i].tangents[0]);
		weightedJoint.TransformNormal(vertex->baseTangents[1], outVertices[i].tangents[1]);

		// Also copy the texture coords
		outVertices[i].st[0] = vertex->st[0];
		outVertices[i].st[1] = vertex->st[1];

		// Also set the color
		outVertices[i].color[0] = 255;
		outVertices[i].color[1] = 255;
		outVertices[i].color[2] = 255;
		outVertices[i].color[3] = 255;

		// Also add to bounds
		outBounds.AddPoint(outVertices[i].xyz);
	}
}

/*
 ==================
 R_ComputeFacePlanes
 ==================
*/
static void R_ComputeFacePlanes (int numIndices, glIndex_t *indices, int numVertices, glVertex_t *vertices, odPlane *facePlanes){

	odPlane		*facePlane;
	glVertex_t	*v0, *v1, *v2;
	int			i;

	for (i = 0, facePlane = facePlanes; i < numIndices; i += 3, facePlane++){
		v0 = &vertices[indices[i+0]];
		v1 = &vertices[indices[i+1]];
		v2 = &vertices[indices[i+2]];

		// Compute the face plane
		facePlane->normal.Cross(v2->xyz - v0->xyz, v1->xyz - v0->xyz);
		facePlane->normal.NormalizeFast();

		facePlane->dist = v0->xyz.Dot(facePlane->normal);

		facePlane->SetType();
		facePlane->SetSignBits();
	}
}

/*
 ==================
 R_CacheModelAmbientVertices
 ==================
*/
static void R_CacheModelAmbientVertices (mdlModel_t *model, bool dynamic){

	mdlSurface_t	*surface;
	vertexBuffer_t	*vertexBuffer;
	bool			used[MDL_MAX_MESHES];
	int				index, count;
	int				i;

	if (!r_vertexBuffers->integerValue)
		return;

	// Count vertices
	memset(used, 0, sizeof(used));
	count = 0;

	for (i = 0, surface = model->surfaces; i < model->numSurfaces; i++, surface++){
		if (!surface->material->numStages)
			continue;		// Invisible surface

		if (surface->material->deform != DFRM_NONE)
			continue;		// Dynamically deformed surface

		used[i] = true;
		count += surface->numVertices;
	}

	if (!count)
		return;

	// Generate the vertex buffer
	vertexBuffer = R_AllocVertexBuffer(dynamic, NULL, count * sizeof(glVertex_t));

	if (!vertexBuffer)
		return;

	// Cache all the surfaces
	index = 0;

	for (i = 0, surface = model->surfaces; i < model->numSurfaces; i++, surface++){
		if (!used[i])
			continue;

		surface->ambientBuffer = vertexBuffer;
		surface->ambientIndex = index;

		// Cache all the vertices
		R_UpdateVertexBuffer(vertexBuffer, surface->vertices, surface->numVertices * sizeof(glVertex_t), index * sizeof(glVertex_t));

		index += surface->numVertices;
	}
}

/*
 ==================
 R_CacheModelShadowVertices
 ==================
*/
static void R_CacheModelShadowVertices (mdlModel_t *model, bool dynamic){

	mdlSurface_t		*surface;
	vertexBuffer_t		*vertexBuffer;
	glShadowVertex_t	*vertices;
	bool				used[MDL_MAX_MESHES];
	int					index, count;
	int					i, j;

	if (!r_vertexBuffers->integerValue)
		return;

	// Count vertices
	memset(used, 0, sizeof(used));
	count = 0;

	for (i = 0, surface = model->surfaces; i < model->numSurfaces; i++, surface++){
		if (surface->material->flags & MF_NOSHADOWS)
			continue;		// No shadow-casting surface

		used[i] = true;
		count += surface->numVertices;
	}

	if (!count)
		return;

	// Generate the vertex buffer
	vertexBuffer = R_AllocVertexBuffer(dynamic, NULL, count * 2 * sizeof(glShadowVertex_t));

	if (!vertexBuffer)
		return;

	// Cache all the surfaces
	vertices = (glShadowVertex_t *)R_MapVertexBuffer(vertexBuffer);

	index = 0;

	for (i = 0, surface = model->surfaces; i < model->numSurfaces; i++, surface++){
		if (!used[i])
			continue;

		surface->shadowBuffer = vertexBuffer;
		surface->shadowIndex = index;

		// Cache all the vertices
		if (vertices){
			for (j = 0; j < surface->numVertices; j++){
				vertices[0].xyzw[0] = surface->vertices[j].xyz[0];
				vertices[0].xyzw[1] = surface->vertices[j].xyz[1];
				vertices[0].xyzw[2] = surface->vertices[j].xyz[2];
				vertices[0].xyzw[3] = 1.0f;

				vertices[1].xyzw[0] = surface->vertices[j].xyz[0];
				vertices[1].xyzw[1] = surface->vertices[j].xyz[1];
				vertices[1].xyzw[2] = surface->vertices[j].xyz[2];
				vertices[1].xyzw[3] = 0.0f;

				vertices += 2;
			}
		}

		index += surface->numVertices * 2;
	}

	R_UnmapVertexBuffer(vertexBuffer);
}


/*
 ==============================================================================

 MDL LOADING

 ==============================================================================
*/


/*
 ==================
 R_LoadMDL
 ==================
*/
static bool R_LoadMDL (const char *name, mdlModel_t **model, int *size){

	byte			*data;
	mdlHeader_t		*inHeader;
	mdlMesh_t		*inMesh;
	mdlVertex_t		*inVertex;
	mdlTriangle_t	*inTriangle;
	mdlPlane_t		*inFacePlane;
	mdlModel_t		*outModel;
	mdlSurface_t	*outSurface;
	glIndex_t		*outIndex;
	glVertex_t		*outVertex;
	int				*outNeighbor;
	odPlane			*outFacePlane;
	int				ident, version;
	int				numMeshes, numVertices, numTriangles;
	int				i, j;

	// Load the file
	FS_ReadFile(name, (void **)&data);
	if (!data)
		return false;

	*model = outModel = (mdlModel_t *)Mem_Alloc(sizeof(mdlModel_t), TAG_RENDERER);
	*size = sizeof(mdlModel_t);

	// Byte swap the header fields and sanity check
	inHeader = (mdlHeader_t *)data;

	ident = LittleLong(inHeader->ident);
	if (ident != MDL_IDENT)
		Com_Error(false, "R_LoadMDL: wrong file id (%s)", name);

	version = LittleLong(inHeader->version);
	if (version != MDL_VERSION)
		Com_Error(false, "R_LoadMDL: wrong version number (%i should be %i) (%s)", version, MDL_VERSION, name);

	// Load the bounds
	for (i = 0; i < 3; i++){
		outModel->bounds[0][i] = LittleFloat(inHeader->bounds[0][i]);
		outModel->bounds[1][i] = LittleFloat(inHeader->bounds[1][i]);
	}

	// Check the number of meshes
	numMeshes = LittleLong(inHeader->numMeshes);
	if (numMeshes < 1)
		Com_Error(false, "R_LoadMDL: no meshes (%s)", name);
	else if (numMeshes > MDL_MAX_MESHES)
		Com_Error(false, "R_LoadMDL: too many meshes (%s)", name);

	outModel->numSurfaces = numMeshes;

	// Load the meshes
	inMesh = (mdlMesh_t *)(data + LittleLong(inHeader->ofsMeshes));

	outModel->surfaces = outSurface = (mdlSurface_t *)Mem_Alloc(outModel->numSurfaces * sizeof(mdlSurface_t), TAG_RENDERER);
	*size += outModel->numSurfaces * sizeof(mdlSurface_t);

	for (i = 0; i < numMeshes; i++, outSurface++){
		// Check the number of vertices
		numVertices = LittleLong(inMesh->numVertices);
		if (numVertices < 1)
			Com_Error(false, "R_LoadMDL: no vertices (%s)", name);
		else if (numVertices > MDL_MAX_VERTICES)
			Com_Error(false, "R_LoadMDL: too many vertices (%s)", name);

		outSurface->numVertices = numVertices;

		// Check the number of triangles
		numTriangles = LittleLong(inMesh->numTriangles);
		if (numTriangles < 1)
			Com_Error(false, "R_LoadMDL: no triangles (%s)", name);
		else if (numTriangles > MDL_MAX_TRIANGLES)
			Com_Error(false, "R_LoadMDL: too many triangles (%s)", name);

		outSurface->numIndices = numTriangles * 3;

		// Load the material
		outSurface->material = R_FindMaterial(inMesh->material, MT_GENERIC);

		// Load the bounds
		for (j = 0; j < 3; j++){
			outSurface->bounds[0][j] = LittleFloat(inMesh->bounds[0][j]);
			outSurface->bounds[1][j] = LittleFloat(inMesh->bounds[1][j]);
		}

		// Load the vertices
		inVertex = (mdlVertex_t *)((byte *)inMesh + LittleLong(inMesh->ofsVertices));

		outSurface->vertices = outVertex = (glVertex_t *)Mem_Alloc(outSurface->numVertices * sizeof(glVertex_t), TAG_RENDERER);
		*size += outSurface->numVertices * sizeof(glVertex_t);

		for (j = 0; j < numVertices; j++, inVertex++, outVertex++){
			outVertex->xyz[0] = LittleFloat(inVertex->xyz[0]);
			outVertex->xyz[1] = LittleFloat(inVertex->xyz[1]);
			outVertex->xyz[2] = LittleFloat(inVertex->xyz[2]);

			outVertex->normal[0] = LittleFloat(inVertex->normal[0]);
			outVertex->normal[1] = LittleFloat(inVertex->normal[1]);
			outVertex->normal[2] = LittleFloat(inVertex->normal[2]);

			outVertex->tangents[0][0] = LittleFloat(inVertex->tangents[0][0]);
			outVertex->tangents[0][1] = LittleFloat(inVertex->tangents[0][1]);
			outVertex->tangents[0][2] = LittleFloat(inVertex->tangents[0][2]);
			outVertex->tangents[1][0] = LittleFloat(inVertex->tangents[1][0]);
			outVertex->tangents[1][1] = LittleFloat(inVertex->tangents[1][1]);
			outVertex->tangents[1][2] = LittleFloat(inVertex->tangents[1][2]);

			outVertex->st[0] = LittleFloat(inVertex->st[0]);
			outVertex->st[1] = LittleFloat(inVertex->st[1]);

			outVertex->color[0] = inVertex->color[0];
			outVertex->color[1] = inVertex->color[1];
			outVertex->color[2] = inVertex->color[2];
			outVertex->color[3] = inVertex->color[3];
		}

		// Load the triangles
		inTriangle = (mdlTriangle_t *)((byte *)inMesh + LittleLong(inMesh->ofsTriangles));

		outSurface->indices = outIndex = (glIndex_t *)Mem_Alloc(outSurface->numIndices * sizeof(glIndex_t), TAG_RENDERER);
		*size += outSurface->numIndices * sizeof(glIndex_t);

		outSurface->neighbors = outNeighbor = (int *)Mem_Alloc(outSurface->numIndices * sizeof(int), TAG_RENDERER);
		*size += outSurface->numIndices * sizeof(int);

		for (j = 0; j < numTriangles; j++, inTriangle++, outIndex += 3, outNeighbor += 3){
			outIndex[0] = LittleLong(inTriangle->indices[0]);
			outIndex[1] = LittleLong(inTriangle->indices[1]);
			outIndex[2] = LittleLong(inTriangle->indices[2]);

			outNeighbor[0] = LittleLong(inTriangle->neighbors[0]);
			outNeighbor[1] = LittleLong(inTriangle->neighbors[1]);
			outNeighbor[2] = LittleLong(inTriangle->neighbors[2]);
		}

		// Load the face planes
		inFacePlane = (mdlPlane_t *)((byte *)inMesh + LittleLong(inMesh->ofsFacePlanes));

		outSurface->facePlanes = outFacePlane = (odPlane *)Mem_Alloc(numTriangles * sizeof(odPlane), TAG_RENDERER);
		*size += numTriangles * sizeof(odPlane);

		for (j = 0; j < numTriangles; j++, inFacePlane++, outFacePlane++){
			outFacePlane->normal[0] = LittleFloat(inFacePlane->normal[0]);
			outFacePlane->normal[1] = LittleFloat(inFacePlane->normal[1]);
			outFacePlane->normal[2] = LittleFloat(inFacePlane->normal[2]);

			outFacePlane->dist = LittleFloat(inFacePlane->dist);

			outFacePlane->SetType();
			outFacePlane->SetSignBits();
		}

		// Clear the vertex buffers
		outSurface->ambientBuffer = NULL;
		outSurface->shadowBuffer = NULL;

		outSurface->ambientIndex = 0;
		outSurface->shadowIndex = 0;

		// Go to the next mesh
		inMesh = (mdlMesh_t *)((byte *)inMesh + LittleLong(inMesh->ofsEnd));
	}

	// Free file data
	FS_FreeFile(data);

	// Try to cache ambient and shadow vertices in static vertex buffers
	R_CacheModelAmbientVertices(outModel, false);
	R_CacheModelShadowVertices(outModel, false);

	return true;
}


/*
 ==============================================================================

 MD5 LOADING

 ==============================================================================
*/


/*
static void R_FinishMD5 (md5Model_t *model, int *size){

	md5Joint_t		*joint;
	md5Surface_t	*surface;
	glIndex_t		*index;
	md5Vertex_t		*vertex, *v0, *v1, *v2;
	md5Weight_t		*weight;
	int				*neighbor;
	odVec5			edges[2];
	odVec3			normal, tangents[2];
	odVec3			tmp;
	float			d;
	int				numTriangles;
	int				i, j, k;

	for (i = 0, surface = model->surfaces; i < model->numSurfaces; i++, surface++){
		// Compute base vertices
		for (j = 0, vertex = surface->vertices; j < surface->numVertices; j++, vertex++){
			vertex->baseXyz.Clear();
			vertex->baseNormal.Clear();
			vertex->baseTangents[0].Clear();
			vertex->baseTangents[1].Clear();

			weight = surface->weights + vertex->firstWeight;

			for (k = 0; k < vertex->numWeights; k++, weight++){
				joint = model->joints + weight->joint;

				joint->matrix.TransformVector(weight->offset, tmp);

				vertex->baseXyz += tmp * weight->jointWeight;
			}
		}

		// Compute base normals and tangents
		for (j = 0, index = surface->indices; j < surface->numIndices; j += 3, index += 3){
			v0 = &surface->vertices[index[0]];
			v1 = &surface->vertices[index[1]];
			v2 = &surface->vertices[index[2]];

			// Find edges
			edges[0][0] = v1->baseXyz[0] - v0->baseXyz[0];
			edges[0][1] = v1->baseXyz[1] - v0->baseXyz[1];
			edges[0][2] = v1->baseXyz[2] - v0->baseXyz[2];
			edges[0][3] = v1->st[0] - v0->st[0];
			edges[0][4] = v1->st[1] - v0->st[1];

			edges[1][0] = v2->baseXyz[0] - v0->baseXyz[0];
			edges[1][1] = v2->baseXyz[1] - v0->baseXyz[1];
			edges[1][2] = v2->baseXyz[2] - v0->baseXyz[2];
			edges[1][3] = v2->st[0] - v0->st[0];
			edges[1][4] = v2->st[1] - v0->st[1];

			// Compute normal vector
			normal[0] = edges[1][1] * edges[0][2] - edges[1][2] * edges[0][1];
			normal[1] = edges[1][2] * edges[0][0] - edges[1][0] * edges[0][2];
			normal[2] = edges[1][0] * edges[0][1] - edges[1][1] * edges[0][0];

			normal.Normalize();

			// Compute first tangent vector
			tangents[0][0] = edges[1][4] * edges[0][0] - edges[1][0] * edges[0][4];
			tangents[0][1] = edges[1][4] * edges[0][1] - edges[1][1] * edges[0][4];
			tangents[0][2] = edges[1][4] * edges[0][2] - edges[1][2] * edges[0][4];

			tangents[0].Normalize();

			// Compute second tangent vector
			tangents[1][0] = edges[1][0] * edges[0][3] - edges[1][3] * edges[0][0];
			tangents[1][1] = edges[1][1] * edges[0][3] - edges[1][3] * edges[0][1];
			tangents[1][2] = edges[1][2] * edges[0][3] - edges[1][3] * edges[0][2];

			tangents[1].Normalize();

			// Inverse tangent vectors if needed
			tmp.Cross(tangents[1], tangents[0]);
			if (tmp.Dot(normal) < 0.0f){
				tangents[0] = -tangents[0];
				tangents[1] = -tangents[1];
			}

			// Add the vectors
			v0->baseNormal += normal;
			v1->baseNormal += normal;
			v2->baseNormal += normal;

			v0->baseTangents[0] += tangents[0];
			v1->baseTangents[0] += tangents[0];
			v2->baseTangents[0] += tangents[0];

			v0->baseTangents[1] += tangents[1];
			v1->baseTangents[1] += tangents[1];
			v2->baseTangents[1] += tangents[1];
		}

		// Renormalize the base normals and tangents
		for (j = 0, vertex = surface->vertices; j < surface->numVertices; j++, vertex++){
			vertex->baseNormal.Normalize();

			d = vertex->baseTangents[0].Dot(vertex->baseNormal);
			vertex->baseTangents[0] -= vertex->baseNormal * d;
			vertex->baseTangents[0].Normalize();

			d = vertex->baseTangents[1].Dot(vertex->baseNormal);
			vertex->baseTangents[1] -= vertex->baseNormal * d;
			vertex->baseTangents[1].Normalize();
		}

		// Build triangle neighbors
		surface->neighbors = neighbor = (int *)Mem_Alloc(surface->numIndices * sizeof(int), TAG_RENDERER);
		*size += surface->numIndices * sizeof(int);

		numTriangles = surface->numIndices / 3;

		for (j = 0, index = surface->indices; j < numTriangles; j++, index += 3, neighbor += 3){
			neighbor[0] = R_FindTriangleNeighbor(numTriangles, surface->indices, index[1], index[0], j);
			neighbor[1] = R_FindTriangleNeighbor(numTriangles, surface->indices, index[2], index[1], j);
			neighbor[2] = R_FindTriangleNeighbor(numTriangles, surface->indices, index[0], index[2], j);
		}
	}

	// Now that we have computed the base vertices for the default pose,
	// inverse the joint matrices
	for (i = 0, joint = model->joints; i < model->numJoints; i++, joint++)
		joint->matrix = -joint->matrix;
}


static void R_LoadMD5Vertices (script_t *script, const char *name, md5Surface_t *surface, int *size){

	token_t		token;
	md5Vertex_t	*out;
	int			numVertices;
	int			i, j, index;

	// Parse the number of vertices
	if (!PS_ExpectTokenString(script, &token, "numVerts"))
		Com_Error(false, "R_LoadMD5: missing 'numVerts' (%s)", name);

	if (!PS_ReadSignedInt(script, &numVertices))
		Com_Error(false, "R_LoadMD5: missing 'numVerts' parameter (%s)", name);

	if (numVertices < 1)
		Com_Error(false, "R_LoadMD5: no vertices (%s)", name);
	else if (numVertices > MD5_MAX_VERTICES)
		Com_Error(false, "R_LoadMD5: too many vertices (%s)", name);

	// Parse the vertices
	surface->numVertices = numVertices;
	surface->vertices = out = (md5Vertex_t *)Mem_Alloc(surface->numVertices * sizeof(md5Vertex_t), TAG_RENDERER);

	*size += surface->numVertices * sizeof(md5Vertex_t);

	for (i = 0; i < numVertices; i++, out++){
		// Parse the vertex index
		if (!PS_ExpectTokenString(script, &token, "vert"))
			Com_Error(false, "R_LoadMD5: missing 'vert' (%s)", name);

		if (!PS_ReadSignedInt(script, &index))
			Com_Error(false, "R_LoadMD5: missing 'vert' parameters (%s)", name);

		if (index != i)
			Com_Error(false, "R_LoadMD5: wrong 'vert' index (%i should be %i) (%s)", index, i, name);

		// Parse the texture coords
		if (!PS_ExpectTokenString(script, &token, "("))
			Com_Error(false, "R_LoadMD5: missing 'vert' parameters (%s)", name);

		for (j = 0; j < 2; j++){
			if (!PS_ReadFloat(script, &out->st[j]))
				Com_Error(false, "R_LoadMD5: missing 'vert' parameters (%s)", name);
		}

		if (!PS_ExpectTokenString(script, &token, ")"))
			Com_Error(false, "R_LoadMD5: missing 'vert' parameters (%s)", name);

		// Parse the first weight index
		if (!PS_ReadSignedInt(script, &out->firstWeight))
			Com_Error(false, "R_LoadMD5: missing 'vert' parameters (%s)", name);

		if (out->firstWeight < 0)
			Com_Error(false, "R_LoadMD5: invalid 'vert' parameters (%s)", name);

		// Parse the number of weights
		if (!PS_ReadSignedInt(script, &out->numWeights))
			Com_Error(false, "R_LoadMD5: missing 'vert' parameters (%s)", name);

		if (out->numWeights < 1)
			Com_Error(false, "R_LoadMD5: invalid 'vert' parameters (%s)", name);
	}
}


static void R_LoadMD5Triangles (script_t *script, const char *name, md5Surface_t *surface, int *size){

	token_t		token;
	glIndex_t	*out;
	int			numTriangles;
	int			i, j, index;

	// Parse the number of triangles
	if (!PS_ExpectTokenString(script, &token, "numTris"))
		Com_Error(false, "R_LoadMD5: missing 'numTris' (%s)", name);

	if (!PS_ReadSignedInt(script, &numTriangles))
		Com_Error(false, "R_LoadMD5: missing 'numTris' parameter (%s)", name);

	if (numTriangles < 1)
		Com_Error(false, "R_LoadMD5: no triangles (%s)", name);
	else if (numTriangles > MD5_MAX_TRIANGLES)
		Com_Error(false, "R_LoadMD5: too many triangles (%s)", name);

	// Parse the triangles
	surface->numIndices = numTriangles * 3;
	surface->indices = out = (glIndex_t *)Mem_Alloc(surface->numIndices * sizeof(glIndex_t), TAG_RENDERER);

	*size += surface->numIndices * sizeof(glIndex_t);

	for (i = 0; i < numTriangles; i++, out += 3){
		// Parse the triangle index
		if (!PS_ExpectTokenString(script, &token, "tri"))
			Com_Error(false, "R_LoadMD5: missing 'tri' (%s)", name);

		if (!PS_ReadSignedInt(script, &index))
			Com_Error(false, "R_LoadMD5: missing 'tri' parameters (%s)", name);

		if (index != i)
			Com_Error(false, "R_LoadMD5: wrong 'tri' index (%i should be %i) (%s)", index, i, name);

		// Parse the indices
		for (j = 0; j < 3; j++){
			if (!PS_ReadUnsignedInt(script, &out[j]))
				Com_Error(false, "R_LoadMD5: missing 'tri' parameters (%s)", name);

			if (out[j] >= surface->numVertices)
				Com_Error(false, "R_LoadMD5: invalid 'tri' parameters (%s)", name);
		}
	}
}

static void R_LoadMD5Weights (script_t *script, const char *name, md5Surface_t *surface, int *size){

	token_t		token;
	md5Weight_t	*out;
	int			numWeights;
	int			i, j, index;

	// Parse the number of weights
	if (!PS_ExpectTokenString(script, &token, "numWeights"))
		Com_Error(false, "R_LoadMD5: missing 'numWeights' (%s)", name);

	if (!PS_ReadSignedInt(script, &numWeights))
		Com_Error(false, "R_LoadMD5: missing 'numWeights' parameter (%s)", name);

	if (numWeights < 1)
		Com_Error(false, "R_LoadMD5: no weights (%s)", name);
	else if (numWeights > MD5_MAX_WEIGHTS)
		Com_Error(false, "R_LoadMD5: too many weights (%s)", name);

	// Parse the weights
	surface->numWeights = numWeights;
	surface->weights = out = (md5Weight_t *)Mem_Alloc(surface->numWeights * sizeof(md5Weight_t), TAG_RENDERER);

	*size += surface->numWeights * sizeof(md5Weight_t);

	for (i = 0; i < numWeights; i++, out++){
		// Parse the weight index
		if (!PS_ExpectTokenString(script, &token, "weight"))
			Com_Error(false, "R_LoadMD5: missing 'weight' (%s)", name);

		if (!PS_ReadSignedInt(script, &index))
			Com_Error(false, "R_LoadMD5: missing 'weight' parameters (%s)", name);

		if (index != i)
			Com_Error(false, "R_LoadMD5: wrong 'weight' index (%i should be %i) (%s)", index, i, name);

		// Parse the joint index
		if (!PS_ReadSignedInt(script, &out->joint))
			Com_Error(false, "R_LoadMD5: missing 'weight' parameters (%s)", name);

		if (out->joint < 0)
			Com_Error(false, "R_LoadMD5: invalid 'weight' parameters (%s)", name);

		// Parse the joint weight
		if (!PS_ReadFloat(script, &out->jointWeight))
			Com_Error(false, "R_LoadMD5: missing 'weight' parameters (%s)", name);

		// Parse the offset
		if (!PS_ExpectTokenString(script, &token, "("))
			Com_Error(false, "R_LoadMD5: missing 'weight' parameters (%s)", name);

		for (j = 0; j < 3; j++){
			if (!PS_ReadFloat(script, &out->offset[j]))
				Com_Error(false, "R_LoadMD5: missing 'weight' parameters (%s)", name);
		}

		if (!PS_ExpectTokenString(script, &token, ")"))
			Com_Error(false, "R_LoadMD5: missing 'weight' parameters (%s)", name);
	}
}

static void R_LoadMD5Joints (script_t *script, const char *name, md5Model_t *model, int *size){

	token_t		token;
	md5Joint_t	*out;
	odVec3		translation;
	odQuat		orientation;
	int			i, j;

	// Skip 'joints'
	if (!PS_ExpectTokenString(script, &token, "joints"))
		Com_Error(false, "R_LoadMD5: missing 'joints' (%s)", name);

	// Skip the opening brace
	if (!PS_ExpectTokenString(script, &token, "{"))
		Com_Error(false, "R_LoadMD5: missing 'joint' parameters (%s)", name);

	// Parse the joints
	model->joints = out = (md5Joint_t *)Mem_Alloc(model->numJoints * sizeof(md5Joint_t), TAG_RENDERER);
	*size += model->numJoints * sizeof(md5Joint_t);

	for (i = 0; i < model->numJoints; i++, out++){
		// Skip the name
		if (!PS_ReadToken(script, &token))
			Com_Error(false, "R_LoadMD5: missing 'joint' parameters (%s)", name);

		// Parse the parent index
		if (!PS_ReadSignedInt(script, &out->parent))
			Com_Error(false, "R_LoadMD5: missing 'joint' parameters (%s)", name);

		if (out->parent < -1 || out->parent >= model->numJoints)
			Com_Error(false, "R_LoadMD5: invalid 'joint' parameters (%s)", name);

		// Parse the translation
		if (!PS_ExpectTokenString(script, &token, "("))
			Com_Error(false, "R_LoadMD5: missing 'joint' parameters (%s)", name);

		for (j = 0; j < 3; j++){
			if (!PS_ReadFloat(script, &translation[j]))
				Com_Error(false, "R_LoadMD5: missing 'joint' parameters (%s)", name);
		}

		if (!PS_ExpectTokenString(script, &token, ")"))
			Com_Error(false, "R_LoadMD5: missing 'joint' parameters (%s)", name);

		// Parse the orientation
		if (!PS_ExpectTokenString(script, &token, "("))
			Com_Error(false, "R_LoadMD5: missing 'joint' parameters (%s)", name);

		for (j = 0; j < 3; j++){
			if (!PS_ReadFloat(script, &orientation[j]))
				Com_Error(false, "R_LoadMD5: missing 'joint' parameters (%s)", name);
		}

		orientation.CalcW();

		out->matrix.Set(orientation, translation);

		if (!PS_ExpectTokenString(script, &token, ")"))
			Com_Error(false, "R_LoadMD5: missing 'joint' parameters (%s)", name);
	}

	// Skip the closing brace
	if (!PS_ExpectTokenString(script, &token, "}"))
		Com_Error(false, "R_LoadMD5: missing 'joint' parameters (%s)", name);
}

static void R_LoadMD5Meshes (script_t *script, const char *name, md5Model_t *model, int *size){

	token_t			token;
	md5Surface_t	*out;
	md5Vertex_t		*vertex;
	md5Weight_t		*weight;
	int				i, j;

	// Parse the meshes
	model->surfaces = out = (md5Surface_t *)Mem_Alloc(model->numSurfaces * sizeof(md5Surface_t), TAG_RENDERER);
	*size += model->numSurfaces * sizeof(md5Surface_t);

	for (i = 0; i < model->numSurfaces; i++, out++){
		// Skip 'mesh'
		if (!PS_ExpectTokenString(script, &token, "mesh"))
			Com_Error(false, "R_LoadMD5: missing 'mesh' (%s)", name);

		// Skip the opening brace
		if (!PS_ExpectTokenString(script, &token, "{"))
			Com_Error(false, "R_LoadMD5: missing 'mesh' parameters (%s)", name);

		// Parse and load the shader
		if (!PS_ExpectTokenString(script, &token, "shader"))
			Com_Error(false, "R_LoadMD5: missing 'shader' (%s)", name);

		if (!PS_ReadToken(script, &token))
			Com_Error(false, "R_LoadMD5: missing 'shader' parameter (%s)", name);

		out->material = R_FindMaterial(token.string, MT_GENERIC);

		// Parse the vertices
		R_LoadMD5Vertices(script, name, out, size);

		// Parse the triangles
		R_LoadMD5Triangles(script, name, out, size);

		// Parse the weights
		R_LoadMD5Weights(script, name, out, size);

		// Skip the closing brace
		if (!PS_ExpectTokenString(script, &token, "}"))
			Com_Error(false, "R_LoadMD5: missing 'mesh' parameters (%s)", name);

		// Sanity check the vertices
		for (j = 0, vertex = out->vertices; j < out->numVertices; j++, vertex++){
			if (vertex->firstWeight + vertex->numWeights > out->numWeights)
				Com_Error(false, "R_LoadMD5: invalid 'vert' parameters (%s)", name);
		}

		// Sanity check the weights
		for (j = 0, weight = out->weights; j < out->numWeights; j++, weight++){
			if (weight->joint >= model->numJoints)
				Com_Error(false, "R_LoadMD5: invalid 'weight' parameters (%s)", name);
		}
	}
}


static bool R_LoadMD5 (const char *name, md5Model_t **model, int *size){

	script_t	*script;
	token_t		token;
	md5Model_t	*out;
	char		*text;
	int			version;

	// Load the file
	script = PS_LoadScriptFile(name);
	if (!script)
		return false;

	PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_ALLOWPATHNAMES);

	*model = out = (md5Model_t *)Mem_Alloc(sizeof(md5Model_t), TAG_RENDERER);
	*size = sizeof(md5Model_t);

	// Parse the version
	if (!PS_ExpectTokenString(script, &token, MD5_VERSION_STRING))
		Com_Error(false, "R_LoadMD5: missing '%s' (%s)", MD5_VERSION_STRING, name);

	if (!PS_ReadSignedInt(script, &version))
		Com_Error(false, "R_LoadMD5: missing version number (%s)", name);

	if (version != MD5_VERSION)
		Com_Error(false, "R_LoadMD5: wrong version number (%i should be %i) (%s)", version, MD5_VERSION, name);

	// Skip the command line
	if (!PS_ExpectTokenString(script, &token, "commandLine"))
		Com_Error(false, "R_LoadMD5: missing 'commandLine' (%s)", name);

	text = Str_FindChar(script->text, '\"');
	if (text)
		text = Str_FindChar(text+1, '\"');

	if (text)
		script->text = text+1;
	else
		Com_Error(false, "R_LoadMD5: missing 'commandLine' parameters (%s)", name);

	// Parse the number of joints
	if (!PS_ExpectTokenString(script, &token, "numJoints"))
		Com_Error(false, "R_LoadMD5: missing 'numJoints' (%s)", name);

	if (!PS_ReadSignedInt(script, &out->numJoints))
		Com_Error(false, "R_LoadMD5: missing 'numJoints' parameter (%s)", name);

	if (out->numJoints < 1)
		Com_Error(false, "R_LoadMD5: no joints (%s)", name);
	else if (out->numJoints > MD5_MAX_JOINTS)
		Com_Error(false, "R_LoadMD5: too many joints (%s)", name);

	// Parse the number of meshes
	if (!PS_ExpectTokenString(script, &token, "numMeshes"))
		Com_Error(false, "R_LoadMD5: missing 'numMeshes' (%s)", name);

	if (!PS_ReadSignedInt(script, &out->numSurfaces))
		Com_Error(false, "R_LoadMD5: missing 'numMeshes' parameter (%s)", name);

	if (out->numSurfaces < 1)
		Com_Error(false, "R_LoadMD5: no meshes (%s)", name);
	else if (out->numSurfaces > MD5_MAX_MESHES)
		Com_Error(false, "R_LoadMD5: too many meshes (%s)", name);

	// Parse the joints
	R_LoadMD5Joints(script, name, out, size);

	// Parse the meshes
	R_LoadMD5Meshes(script, name, out, size);

	// Precompute base vertices and build triangle neighbors
	R_FinishMD5(out, size);

	// Free file data
	PS_FreeScript(script);

	return true;
}
*/

/*
 ==============================================================================

 MODEL LOADING

 ==============================================================================
*/


/*
 ==================
 R_LoadModel
 ==================
*/
static model_t *R_LoadModel (const char *name, modelType_t type, void *data, int size){

	model_t	*model;
	uint	hashKey;

	if (r_numModels == MAX_MODELS)
		Com_Error(false, "R_LoadModel: MAX_MODELS hit");

	r_models[r_numModels++] = model = (model_t *)Mem_Alloc(sizeof(model_t), TAG_RENDERER);

	// Fill it in
	Str_Copy(model->name, name, sizeof(model->name));
	model->type = type;
	model->data = data;
	model->size = size;

	// Add to hash table
	hashKey = Str_HashKey(model->name, MODELS_HASH_SIZE, false);

	model->nextHash = r_modelsHashTable[hashKey];
	r_modelsHashTable[hashKey] = model;

	return model;
}

/*
 ==================
 R_FindModel
 ==================
*/
static model_t *R_FindModel (const char *name){

	model_t	*model;
	char	extension[16];
	void	*data;
	int		size;
	uint	hashKey;

	// See if already loaded
	hashKey = Str_HashKey(name, MODELS_HASH_SIZE, false);

	for (model = r_modelsHashTable[hashKey]; model; model = model->nextHash){
		if (!Str_ICompare(model->name, name))
			return model;
	}

	// Load it from disk
	Str_ExtractFileExtension(name, extension, sizeof(extension));

	if (!Str_ICompare(extension, ".mdl")){
		if (R_LoadMDL(name, (mdlModel_t **)&data, &size))
			return R_LoadModel(name, MODEL_MDL, data, size);
	}

	if (!Str_ICompare(extension, ".md5mesh")){
//		if (R_LoadMD5(name, (md5Model_t **)&data, &size))
//			return R_LoadModel(name, MODEL_MD5, data, size);
	}

	// Not found
	return NULL;
}

/*
 ==================
 R_RegisterModel
 ==================
*/
model_t *R_RegisterModel (const char *name){

	model_t	*model;

	model = R_FindModel(name);
	if (model)
		return model;

	// Register the name even if not found
	Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find model '%s'\n", name);

	return R_LoadModel(name, MODEL_BAD, NULL, 0);
}


// ============================================================================


/*
 ==================
 R_LoadInlineModel

 Used by the BSP loading code for inline models
 ==================
*/
void R_LoadInlineModel (int index, void *data){

	char	name[8];

	Str_SPrintf(name, sizeof(name), "*%i", index);

	R_LoadModel(name, MODEL_INLINE, data, 0);
}

/*
 ==================
 R_InstantiateDynamicModel

 Returns a static model instance for a dynamic model
 ==================
*/
/*
mdlModel_t *R_InstantiateDynamicModel (entity_t *entity, bool cacheAmbient, bool cacheShadow){

	md5Model_t		*cachedModel;
	md5Surface_t	*cachedSurface;
	mdlModel_t		*instancedModel;
	mdlSurface_t	*instancedSurface;
	odJointMat		jointMatrices[MD5_MAX_JOINTS];
	int				i;

	if (!entity->e.model)
		Com_Error(false, "R_InstantiateDynamicModel: NULL model");

	if (entity->e.model->type != MODEL_MD5)
		Com_Error(false, "R_InstantiateDynamicModel: invalid model type for '%s", entity->e.model->name);

	// See if we have already cached this instance
	if (entity->instance){
		instancedModel = entity->instance;

		// If needed, try to cache ambient and shadow vertices in dynamic
		// vertex buffers
		if (cacheAmbient && !instancedModel->surfaces->ambientBuffer)
			R_CacheModelAmbientVertices(instancedModel, true);

		if (cacheShadow && !instancedModel->surfaces->shadowBuffer)
			R_CacheModelShadowVertices(instancedModel, true);

		return instancedModel;
	}

	cachedModel = (md5Model_t *)entity->e.model->data;

	// Multiply the joints
	R_MultiplyJoints(cachedModel->numJoints, cachedModel->joints, entity->e.joints, jointMatrices);

	// Instantiate the model
	instancedModel = (mdlModel_t *)Mem_Alloc(sizeof(mdlModel_t), TAG_RENDERER);

	instancedModel->bounds.Clear();

	instancedModel->numSurfaces = cachedModel->numSurfaces;
	instancedModel->surfaces = instancedSurface = (mdlSurface_t *)Mem_Alloc(instancedModel->numSurfaces * sizeof(mdlSurface_t), TAG_RENDERER);

	// Instantiate all the surfaces
	for (i = 0, cachedSurface = cachedModel->surfaces; i < cachedModel->numSurfaces; i++, cachedSurface++){
		tr.pc.dynamicMD5++;
		tr.pc.dynamicIndices += cachedSurface->numIndices;
		tr.pc.dynamicVertices += cachedSurface->numVertices;

		// Copy the material
		instancedSurface->material = cachedSurface->material;

		// Indices just point back at the original surface indices
		instancedSurface->numIndices = cachedSurface->numIndices;
		instancedSurface->indices = cachedSurface->indices;

		// Allocate the vertices
		instancedSurface->numVertices = cachedSurface->numVertices;
		instancedSurface->vertices = (glVertex_t *)Mem_Alloc(instancedSurface->numVertices * sizeof(glVertex_t), TAG_RENDERER);

		// Neighbors just point back at the original surface neighbors
		instancedSurface->neighbors = cachedSurface->neighbors;

		// Allocate the face planes
		instancedSurface->facePlanes = (odPlane *)Mem_Alloc((instancedSurface->numIndices / 3) * sizeof(odPlane), TAG_RENDERER);

		// Clear the vertex buffers
		instancedSurface->ambientBuffer = NULL;
		instancedSurface->shadowBuffer = NULL;

		instancedSurface->ambientIndex = 0;
		instancedSurface->shadowIndex = 0;

		// Transform vertices
		R_TransformVertices(cachedSurface->numVertices, cachedSurface->vertices, cachedSurface->weights, jointMatrices, instancedSurface->vertices, instancedSurface->bounds);

		// Compute face planes
		R_ComputeFacePlanes(cachedSurface->numIndices, cachedSurface->indices, instancedSurface->numVertices, instancedSurface->vertices, instancedSurface->facePlanes);

		// Also add to bounds
		instancedModel->bounds.AddBounds(instancedSurface->bounds);

		instancedSurface++;
	}

	// If needed, try to cache ambient and shadow vertices in dynamic vertex
	// buffers
	if (cacheAmbient)
		R_CacheModelAmbientVertices(instancedModel, true);

	if (cacheShadow)
		R_CacheModelShadowVertices(instancedModel, true);

	// Set up the new model instance
	entity->instance = instancedModel;

	return instancedModel;
}
*/
/*
 ==================
 R_ClearDynamicModelInstance
 ==================
*/
void R_ClearDynamicModelInstance (entity_t *entity){

	mdlModel_t		*instancedModel;
	mdlSurface_t	*instancedSurface;
	int				i;

	if (!entity->instance)
		return;

	instancedModel = entity->instance;

	// Free the vertices and face planes
	for (i = 0, instancedSurface = instancedModel->surfaces; i < instancedModel->numSurfaces; i++, instancedSurface++){
		Mem_Free(instancedSurface->vertices);
		Mem_Free(instancedSurface->facePlanes);
	}

	// Vertex buffers are shared by all surfaces, and may be NULL
	if (instancedModel->surfaces->ambientBuffer)
		R_FreeVertexBuffer(instancedModel->surfaces->ambientBuffer);
	if (instancedModel->surfaces->shadowBuffer)
		R_FreeVertexBuffer(instancedModel->surfaces->shadowBuffer);

	// Free the surfaces and model
	Mem_Free(instancedModel->surfaces);
	Mem_Free(instancedModel);

	// Clear the model instance
	entity->instance = NULL;
}

/*
 ==================
 R_WorldMapInfo_f
 ==================
*/
static void R_WorldMapInfo_f (void){

	if (!tr.world){
		Com_Printf("World map not loaded\n");
		return;
	}

	Com_Printf("\n");
	Com_Printf("%s\n", tr.world->name);
	Com_Printf("----------------------------------------\n");
	Com_Printf("%5i KB in %8i planes\n", Mem_Size(tr.world->planes) >> 10, tr.world->numPlanes);
	Com_Printf("%5i KB in %8i indices\n", Mem_Size(tr.world->indices) >> 10, tr.world->numIndices);
	Com_Printf("%5i KB in %8i vertices\n", Mem_Size(tr.world->vertices) >> 10, tr.world->numVertices);
	Com_Printf("%5i KB in %8i neighbors\n", Mem_Size(tr.world->neighbors) >> 10, tr.world->numNeighbors);
	Com_Printf("%5i KB in %8i face planes\n", (tr.world->numFacePlanes) ? (Mem_Size(tr.world->facePlanes) >> 10) : 0, tr.world->numFacePlanes);
	Com_Printf("%5i KB in %8i foliage instances\n", (tr.world->numFoliageInstances) ? (Mem_Size(tr.world->foliageInstances) >> 10) : 0, tr.world->numFoliageInstances);
	Com_Printf("%5i KB in %8i surfaces\n", Mem_Size(tr.world->surfaces) >> 10, tr.world->numSurfaces);
	Com_Printf("%5i KB in %8i leaf surfaces\n", Mem_Size(tr.world->leafSurfaces) >> 10, tr.world->numLeafSurfaces);
	Com_Printf("%5i KB in %8i nodes / leafs\n", Mem_Size(tr.world->nodes) >> 10, tr.world->numNodes);
	Com_Printf("%5i KB in %8i inline models\n", Mem_Size(tr.world->inlineModels) >> 10, tr.world->numInlineModels);
	Com_Printf("----------------------------------------\n");
	Com_Printf("%.2f MB of world map data\n", tr.world->size * (1.0f/1048576));
	Com_Printf("\n");
}

/*
 ==================
 R_ListModels_f
 ==================
*/
static void R_ListModels_f (void){

	model_t			*model;
	inlineModel_t	*inlineModel;
	mdlModel_t		*mdlModel;
	md5Model_t		*md5Model;
	int				indices, vertices;
	int				bytes = 0;
	int				i, j;

	Com_Printf("\n");
	Com_Printf("      surfs -tris--- -verts-- -size- -name--------\n");

	for (i = 0; i < r_numModels; i++){
		model = r_models[i];

		bytes += model->size;

		Com_Printf("%4i: ", i);

		indices = 0;
		vertices = 0;

		switch (model->type){
		case MODEL_INLINE:
			inlineModel = (inlineModel_t *)model->data;

			Com_Printf("%5i ", inlineModel->numSurfaces);

			for (j = 0; j < inlineModel->numSurfaces; j++){
				indices += inlineModel->surfaces[j].numIndices;
				vertices += inlineModel->surfaces[j].numVertices;
			}

			break;
		case MODEL_MDL:
			mdlModel = (mdlModel_t *)model->data;

			Com_Printf("%5i ", mdlModel->numSurfaces);

			for (j = 0; j < mdlModel->numSurfaces; j++){
				indices += mdlModel->surfaces[j].numIndices;
				vertices += mdlModel->surfaces[j].numVertices;
			}

			break;
		case MODEL_MD5:
			md5Model = (md5Model_t *)model->data;

			Com_Printf("%5i ", md5Model->numSurfaces);

			for (j = 0; j < md5Model->numSurfaces; j++){
				indices += md5Model->surfaces[j].numIndices;
				vertices += md5Model->surfaces[j].numVertices;
			}

			break;
		}

		Com_Printf("%8i %8i ", indices / 3, vertices);

		Com_Printf("%5ik ", model->size >> 10);

		Com_Printf("%s%s\n", model->name, (model->type == MODEL_BAD) ? " (DEFAULTED)" : "");
	}

	Com_Printf("--------------------------------------------------\n");
	Com_Printf("%i total models\n", r_numModels);
	Com_Printf("%.2f MB of model data\n", bytes * (1.0f/1048576));
	Com_Printf("\n");
}

/*
 ==================
 R_InitModels
 ==================
*/
void R_InitModels (void){

	// Register our commands
	Cmd_AddCommand("worldMapInfo", R_WorldMapInfo_f, "Shows world map information");
	Cmd_AddCommand("listModels", R_ListModels_f, "Lists loaded models");
}

/*
 ==================
 R_ShutdownModels
 ==================
*/
void R_ShutdownModels (void){

	// Unregister our commands
	Cmd_RemoveCommand("worldMapInfo");
	Cmd_RemoveCommand("listModels");

	// Clear model list
	memset(r_modelsHashTable, 0, sizeof(r_modelsHashTable));
	memset(r_models, 0, sizeof(r_models));

	r_numModels = 0;
}
